home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Nebula 2
/
Nebula Two.iso
/
SourceCode
/
Palettes
/
Chart
/
Source
/
PieChart.m
< prev
next >
Wrap
Text File
|
1995-06-12
|
19KB
|
744 lines
// -------------------------------------------------------------------------------------
// Pie Chart view
// Martin D. Flynn, NeXT Computer, Inc.
// -------------------------------------------------------------------------------------
#import <appkit/appkit.h>
#import <libc.h>
#import <c.h>
#import <stdlib.h>
#import <string.h>
#import <math.h>
#import <dpsclient/wraps.h>
#import <objc/List.h>
#import <objc/Storage.h>
#import "PieChartPS.h"
#import "PieChart.h"
// -------------------------------------------------------------------------------------
// archive version number
#define VERSION 1
// -------------------------------------------------------------------------------------
#define X origin.x
#define Y origin.y
#define W size.width
#define H size.height
#define pieSLICE(I) ((pieSlice_t*)[dataId elementAt:(I)])
#define matrixCELL(M,I) (pFlags.useCellTags?[M findCellWithTag:I]:[[M cellList] objectAt:I])
#define ibSliceCOUNT ((dftSliceCount>0)?dftSliceCount:3)
// -------------------------------------------------------------------------------------
// percent display format
static char percentFormat[16] = { "%.1f%%" };
// -------------------------------------------------------------------------------------
// pie slice data
typedef struct pieSlice_s {
id labelSrc; // label source for slice
const char *label; // slice label
id valueSrc; // value source for slice
float value; // current slice value
float percent; // current slice percent
float endAngle; // ending angle
} pieSlice_t;
// -------------------------------------------------------------------------------------
@implementation PieChart
// -------------------------------------------------------------------------------------
// archive version number
static BOOL _nibMode = NO;
+ initialize
{
[self setVersion:VERSION];
if (!strcmp([NXApp appName], "InterfaceBuilder")) _nibMode = YES;
return self;
}
// -------------------------------------------------------------------------------------
// return inspector name
- (const char*)getInspectorClassName { return "PieChartInspector"; }
// -------------------------------------------------------------------------------------
// color/gray support
/* convert color to gray */
static float _colorGray(NXColor color)
{
float gray;
NXConvertColorToGray(color, &gray);
return gray;
}
/* set color/gray */
static void _setColor(NXColor color, BOOL isColor)
{
if (isColor) NXSetColor(color); else PSsetgray(_colorGray(color));
}
// -------------------------------------------------------------------------------------
// color table support
/* return gray value for slice number */
static float _grayValue(int i, int n)
{
if ((++i) & 1) return (float)(i + 1) / (float)(2 * n);
return (((float)i / 2.0) + (float)((n + 1) / 2)) / (float)n;
}
- _initColorTable:(int)count clear:(BOOL)clear
{
int i;
/* clear existing table */
if (clear && sliceColors) { [sliceColors free]; sliceColors = (id)nil; }
/* set default slice count */
dftSliceCount = (count >= 0)? count : 0;
if (dftSliceCount <= 0) return self;
/* build table */
if (!sliceColors) {
sliceColors = [[[Storage alloc] initCount:1 elementSize:sizeof(NXColor)
description:(char*)nil] empty];
}
/* fill table */
for (i = [sliceColors count]; i < dftSliceCount; i++) {
NXColor color = NXConvertGrayToColor(_grayValue(i, dftSliceCount));
[sliceColors addElement:&color];
}
return self;
}
// -------------------------------------------------------------------------------------
// initialize data
/* empty dataId */
- _initPieData
{
int i;
/* create data holder if non-existant */
if (!dataId) {
dataId = [Storage newCount:1 elementSize:sizeof(pieSlice_t) description:(char*)nil];
[dataId empty];
}
/* free any existing data */
if (i = [dataId count]) {
while (i) {
const char *lbl = pieSLICE(--i)->label;
if (lbl) free((char*)lbl);
}
[dataId empty];
}
return dataId;
}
/* fill dataId */
- _fillPieData:(int)count
{
int i;
for (i = 0; i < count; i++) [self setValue:100.0 forSlice:i];
return dataId;
}
/* IB use (_nibMode assumed): fill pie with default data */
- _initDefaultPie:(int)sliceCount
{
[self _initPieData]; // clear existing data
[self _fillPieData:((sliceCount>=0)?sliceCount:ibSliceCOUNT)];
return self;
}
// -------------------------------------------------------------------------------------
// initialize view
/* main init */
- initFrame:(const NXRect*)r
{
/* init view */
[super initFrame:r];
/* init flags */
memset(&pFlags, 0, sizeof(pFlags));
pFlags.showPercent = YES;
pFlags.drawOutline = YES;
pFlags.drawLabels = YES;
pFlags.drawInColor = YES;
pFlags.acceptColor = NO;
/* initialize with defaults */
outlineColor = NXConvertGrayToColor(NX_BLACK);
labelColor = NXConvertGrayToColor(NX_BLACK);
backgroundColor = NXConvertGrayToColor(NX_LTGRAY);
currentFont = [Font newFont:"Helvetica" size:10.0 matrix:NX_IDENTITYMATRIX];
labelMatrix = (id)nil;
valueMatrix = (id)nil;
outputPercent = (id)nil;
delegate = self;
/* init data holder */
dataId = (id)nil;
sliceColors = (id)nil;
[self setDefaultSliceCount:0];
[self _initPieData];
if (_nibMode) [self _fillPieData:ibSliceCOUNT];
return self;
}
/* free pie chart */
- free
{
[[self _initPieData] free];
[self setDefaultSliceCount:0];
return [super free];
}
// -------------------------------------------------------------------------------------
// adding data to the pie chart
/* return cell title */
static const char *_cellTitle(id cellId)
{
const char *label = (const char*)nil;
if (!cellId) return (const char*)nil;
if ([cellId respondsTo:@selector(title)]) label = [cellId title]; else
if ([cellId respondsTo:@selector(stringValue)]) label = [cellId stringValue];
return label;
}
/* fill holes in data */
static void _fillData(id dataId, int limit)
{
int i;
pieSlice_t data = { (id)nil, (const char*)nil, (id)nil, 0.0, 0.0, 0.0 };
for (i = [dataId count]; i <= limit; i++) [dataId addElement:&data];
}
/* set pie slice data */
- _setPieSlice:(int)slice :(id)vs:(float)v:(BOOL)setV :(id)ls:(char*)l:(BOOL)setL
{
pieSlice_t *pieSlice;
/* fill up any holes in data */
if (slice < 0) return (id)nil;
_fillData(dataId, slice);
pieSlice = pieSLICE(slice);
/* set label */
if (setL) {
if (pieSlice->label) free((char*)pieSlice->label);
pieSlice->labelSrc = ls;
pieSlice->label = (!ls && l)? NXCopyStringBuffer(l) : (const char*)nil;
}
/* set value */
if (setV) {
pieSlice->valueSrc = vs;
pieSlice->value = (!vs)? v : 0.0;
}
return self;
}
/* explicitly set a pie slice value (no re-display) */
- setValue:(float)value forSlice:(int)slice
{
return [self _setPieSlice:slice :(id)nil:value:YES :(id)nil:(char*)nil:NO];
}
/* set pie slice value and label */
- setValue:(float)value andLabel:(const char*)label forSlice:(int)slice
{
return [self _setPieSlice:slice :(id)nil:value:YES :(id)nil:(char*)label:YES];
}
/* redisplay */
- update:sender
{
return [self display];
}
// -------------------------------------------------------------------------------------
// set plot options
// -------------------------------------------------------------------------------------
/* set background transparent */
- setBackgroundTransparent:(BOOL)flag
{
pFlags.transparent = flag?YES:NO;
return self;
}
/* return background transparent state */
- (BOOL)backgroundTransparent
{
return pFlags.transparent;
}
/* set font (make a new unflipped font copy) */
// This is done this way on purpose
- setFont:fontId
{
currentFont = [Font newFont:[fontId name] size:[fontId pointSize] matrix:NX_IDENTITYMATRIX];
return self;
}
/* return current font (NX_FLIPPEDMATRIX) */
// This is done this way on purpose (IB can't handle it properly any other way)
- font
{
return [Font newFont:[currentFont name] size:[currentFont pointSize]];
}
/* set background gray */
- setBackgroundGray:(float)aGray
{
backgroundColor = NXConvertGrayToColor(aGray);
return self;
}
/* return current background gray */
- (float)backgroundGray
{
float gray;
NXConvertColorToGray(backgroundColor, &gray);
return gray;
}
/* set background color */
- setBackgroundColor:(NXColor)aColor
{
backgroundColor = aColor;
return self;
}
/* return current background color */
- (NXColor)backgroundColor
{
return backgroundColor;
}
/* set outline gray */
- setOutlineGray:(float)aGray
{
outlineColor = NXConvertGrayToColor(aGray);
return self;
}
/* return current outline gray */
- (float)outlineGray
{
float gray;
NXConvertColorToGray(outlineColor, &gray);
return gray;
}
/* set text label gray */
- setLabelGray:(float)aGray
{
labelColor = NXConvertGrayToColor(aGray);
return self;
}
/* return current label gray */
- (float)labelGray
{
float gray;
NXConvertColorToGray(labelColor, &gray);
return gray;
}
/* set show percent on pie slice */
- setShowPercent:(BOOL)flag
{
pFlags.showPercent = flag?YES:NO;
return self;
}
/* return show percent status */
- (BOOL)showPercent
{
return pFlags.showPercent;
}
/* set pie slice outline */
- setDrawSliceOutline:(BOOL)flag
{
pFlags.drawOutline = flag?YES:NO;
return self;
}
/* return pie slice outline status */
- (BOOL)drawSliceOutline
{
return pFlags.drawOutline;
}
/* set draw label */
- setDrawLabels:(BOOL)flag
{
pFlags.drawLabels = flag?YES:NO;
return self;
}
/* return draw label status */
- (BOOL)drawLabels
{
return pFlags.drawLabels;
}
/* set specific slice color */
- setSliceColor:(NXColor)color at:(int)slice
{
if (slice < 0) return self;
if (slice + 1 > dftSliceCount) [self _initColorTable:slice + 1 clear:NO];
[sliceColors replaceElementAt:slice with:&color];
return self;
}
/* return slice color */
- (NXColor)sliceColorAt:(int)slice
{
int count;
/* check color table */
if (sliceColors && (slice < [sliceColors count])) {
NXColor color = *((NXColor*)[sliceColors elementAt:slice]);
return color;
}
/* return gray */
count = (dftSliceCount > 0)? dftSliceCount : (dataId? [dataId count] : 0);
if (slice >= count) return NXConvertGrayToColor(NX_WHITE);
return NXConvertGrayToColor(_grayValue(slice, count));
}
/* set default slice count */
- setDefaultSliceCount:(int)count
{
return [self _initColorTable:count clear:YES];
}
/* return current default slice count */
- (int)defaultSliceCount
{
return dftSliceCount;
}
/* allow accepting color changes */
- setAcceptColor:(BOOL)flag
{
pFlags.acceptColor = flag;
if (flag) pFlags.drawInColor = YES;
return self;
}
/* set delegate */
- setDelegate:aDelegate
{
delegate = aDelegate;
return self;
}
/* return delegate */
- delegate
{
return delegate;
}
// -------------------------------------------------------------------------------------
// outlets
// -------------------------------------------------------------------------------------
/* load value and label matrix into pie */
- _loadMatrix:sender
{
int i;
id vList = valueMatrix? [valueMatrix cellList] : (id)nil;
id lList = labelMatrix? [labelMatrix cellList] : (id)nil;
[self _initPieData]; // clear existing data
if (!vList) return self;
for (i = 0; i < [vList count]; i++) {
id vCell = [vList objectAt:i];
id lCell = (lList && (i < [lList count]))? [lList objectAt:i] : (id)nil;
[self _setPieSlice:(pFlags.useCellTags?[vCell tag]:i)
:vCell:0.0:YES :lCell:(char*)nil:YES];
}
[self display];
return self;
}
/* set value matrix */
- setValueMatrix:anObject
{
if (![anObject isKindOf:[Matrix class]]) return self;
valueMatrix = anObject;
[self perform:@selector(_loadMatrix:) with:(id)nil afterDelay:1 cancelPrevious:YES];
return self;
}
/* set label matrix */
- setLabelMatrix:anObject
{
if (![anObject isKindOf:[Matrix class]]) return self;
labelMatrix = anObject;
[self perform:@selector(_loadMatrix:) with:(id)nil afterDelay:1 cancelPrevious:YES];
return self;
}
/* set form matrix */
- setFormMatrix:anObject
{
if (![anObject isKindOf:[Matrix class]]) return self;
labelMatrix = valueMatrix = anObject;
[self perform:@selector(_loadMatrix:) with:(id)nil afterDelay:1 cancelPrevious:YES];
return self;
}
/* set output percent matrix */
- setPercentMatrix:anObject
{
if (![anObject isKindOf:[Matrix class]]) return self;
outputPercent = anObject;
return self;
}
// -------------------------------------------------------------------------------------
// plot data in pie chart
// -------------------------------------------------------------------------------------
/* pie chart plot */
- _drawPie
{
int i, dataCnt = [dataId count];
float sum, curAngle, angle;
/* set fonts and gray */
[currentFont set];
PSsetlinewidth(1.0);
/* determine center and radius */
pieCenter.x = bounds.W / 2.0;
pieCenter.y = bounds.H / 2.0;
if (!pFlags.drawLabels) pieRadius = (MIN(bounds.W, bounds.H) - 2.0) / 2.0;
else pieRadius = (MIN(bounds.W, bounds.H) - (4.5 * [currentFont pointSize])) / 2.0;
if (pieRadius < 1.0) pieRadius = 1.0;
/* update values and sum */
for (sum = 0.0, i = 0; i < dataCnt; i++) {
pieSlice_t *slice = pieSLICE(i);
if (slice->valueSrc) slice->value = [slice->valueSrc floatValue];
sum += slice->value;
}
/* draw pie ('slice->value' is current) */
if (sum) {
char *lp, label[256], pct[16];
float oGray, lGray;
/* draw slices */
for (curAngle = 0.0, i = 0; i < dataCnt; i++) {
pieSlice_t *slice = pieSLICE(i);
slice->percent = slice->value * 100.0 / sum;
angle = slice->percent * 3.60;
slice->endAngle = curAngle + angle;
if (!angle) continue;
if (lp = (char*)_cellTitle(slice->labelSrc)) strcpy(label, lp); else
if (slice->label) strcpy(label, slice->label);
else sprintf(label, "#%d", i + 1);
if (pFlags.showPercent) {
sprintf(pct, percentFormat, slice->percent);
sprintf(&label[strlen(label)], " (%s)", pct);
}
_setColor([self sliceColorAt:i], pFlags.drawInColor);
NXConvertColorToGray(outlineColor, &oGray);
NXConvertColorToGray(labelColor, &lGray);
_pieDrawSlice(label, [currentFont pointSize], pieCenter.x, pieCenter.y, pieRadius,
curAngle, slice->endAngle, (pFlags.drawOutline?oGray:-1.0),
(pFlags.drawLabels?lGray:-1.0));
curAngle += angle;
}
/* update output percent matrix */
if (outputPercent) {
for (i = 0; i < dataCnt; i++) {
id cellId = matrixCELL(outputPercent, i);
if (cellId) {
pieSlice_t *slice = pieSLICE(i);
sprintf(pct, percentFormat, slice->percent);
[cellId setStringValue:pct];
}
}
}
}
return self;
}
/* draw pie view */
- drawSelf:(const NXRect *)r :(int)n
{
if (!pFlags.transparent) {
_setColor(backgroundColor, pFlags.drawInColor);
NXRectFill(&bounds);
}
[self _drawPie];
//if (isFirstResponder) NXHighlightRect(&bounds);
return self;
}
// -------------------------------------------------------------------------------------
// object archiving
- read:(NXTypedStream*)s
{
int ver, i, cnt;
sliceColors = (id)nil;
[super read:s];
NXReadTypes(s, "i",&ver);
NXReadTypes(s, "s@",&pFlags,¤tFont);
backgroundColor = NXReadColor(s);
outlineColor = NXReadColor(s);
labelColor = NXReadColor(s);
NXReadTypes(s, "i",&cnt);
[self setDefaultSliceCount:cnt]; // set dftSliceCount
for (i = 0; i < dftSliceCount; i++) [self setSliceColor:NXReadColor(s) at:i];
delegate = self;
labelMatrix = (id)nil;
valueMatrix = (id)nil;
outputPercent = (id)nil;
dataId = (id)nil;
pFlags.drawInColor = YES;
[self _initPieData];
if (_nibMode) [self _fillPieData:ibSliceCOUNT];
return self;
}
- write:(NXTypedStream*)s
{
int ver = [[self class] version];
int i;
[super write:s];
NXWriteTypes(s, "i",&ver);
NXWriteTypes(s, "s@",&pFlags,¤tFont);
NXWriteColor(s, backgroundColor);
NXWriteColor(s, outlineColor);
NXWriteColor(s, labelColor);
NXWriteTypes(s, "i",&dftSliceCount);
for (i = 0; i < dftSliceCount; i++) NXWriteColor(s, [self sliceColorAt:i]);
return self;
}
// -------------------------------------------------------------------------------------
// firstResponder for getting copy/paste commands
/* allow becoming first responder */
- (BOOL)acceptsFirstResponder { return YES; }
/* become first responder */
- becomeFirstResponder
{
isFirstResponder = YES;
[self display];
return self;
}
/* resign first responder */
- resignFirstResponder
{
isFirstResponder = NO;
[self display];
return self;
}
// -------------------------------------------------------------------------------------
// mouseDown tracking
/* handle pie slice selected */
- selectedPieSlice:(int)slice
{
id cellId, matrix = actionMatrix;
if (!matrix && valueMatrix && ([valueMatrix target] != self)) matrix = valueMatrix;
if (matrix && (slice >= 0) && (cellId = matrixCELL(matrix, slice))) {
[matrix selectCell:cellId];
[matrix sendAction];
}
return self;
}
/* slice hit detection */
- (int)_sliceHitDetection:(const NXPoint*)mousePoint
{
int index, dataCnt = dataId? [dataId count] : 0;
float len, xLen, yLen, angle;
NXPoint pt = *mousePoint;
/* check for valid pie */
if (!dataCnt) return -1;
[self convertPoint:&pt fromView:(id)nil];
/* determine distance from center */
xLen = pt.x - pieCenter.x;
yLen = pt.y - pieCenter.y;
len = (float)hypot((double)xLen, (double)yLen);
if (len > pieRadius) return -1;
/* determine angle */
angle = (float)(atan2(yLen, xLen) * 180.0 / M_PI);
if (angle < 0.0) angle += 360.0;
/* find pie slice */
for (index = 0; index < dataCnt; index++) {
pieSlice_t *slice = pieSLICE(index);
if (angle <= slice->endAngle) return index;
}
return -1;
}
/* intercept mouse down */
- mouseDown:(NXEvent*)e
{
int index;
/* IB check */
if (_nibMode && (delegate != self))
return [delegate selectedPieSlice:[self _sliceHitDetection:&(e->location)]];
/* check for slice selection */
if (e->data.mouse.click == 2) {
index = [self _sliceHitDetection:&(e->location)];
if ((index >= 0) && [delegate respondsTo:@selector(selectedPieSlice:)])
[delegate selectedPieSlice:index];
}
return [super mouseDown:e];
}
// -------------------------------------------------------------------------------------
// IB use: color modification
/* accept color */
- acceptColor:(NXColor)color atPoint:(const NXPoint*)point
{
int slice;
if (!pFlags.acceptColor) return self;
slice = [self _sliceHitDetection:point];
if (slice < 0) [self setBackgroundColor:color];
else if (dftSliceCount > 0) [self setSliceColor:color at:slice];
return [self display];
}
@end